extends Node

const DATA_FILEPATH: String = "user://data.json"
const DEFAULTS: Dictionary = {
	"stop_daemon_on_exit": true,
	"media_openers": {},
	"permanent_search_options": {
		"not_tags": [
			"mature",
			"porn",
			"sex",
			"adult",
			"nsfw",
			"xxx",
		],
		"remove_duplicates": true,
		"valid_channel_signature": true,
	},
	"sections": {
		"order": PoolStringArray()
	},
	"comment_server": "https://comments.lbry.com/api/v2",
}

const DEFAULT_MEDIA_OPENER: Dictionary = {
	"command": "",
	"use_streaming": false,
}


func _ready() -> void:
	var file := File.new()
	if not file.file_exists(DATA_FILEPATH):
		var data_file: File = _open_data_file(File.WRITE)
		if data_file:
			_initialize_data_file(data_file)
			data_file.close()


func get_sections_order() -> PoolStringArray:
	"""
	Returns the section names in order or an empty array is unable to retrieve
	them
	"""
	var key1: String = "sections"
	var key2: String = "order"
	var data: Dictionary = get_data()
	var sections_order: PoolStringArray = DEFAULTS[key1][key2]
	if data.get(key1, {}).get(key2) is Array:
		sections_order = PoolStringArray(data[key1][key2])
	return sections_order


func save_sections_order(sections_order: PoolStringArray) -> void:
	"""
	Stores the section names in order
	
	Args:
		sections_order: Names of the sections in the order they should appear
	"""
	var key1: String = "sections"
	var key2: String = "order"
	# `get_data()` before `_open_data_file()` to ensure no problems occur due 
	# to trying to open `DATA_FILEPATH` twice
	var data: Dictionary = get_data()
	var data_file: File = _open_data_file(File.WRITE)
	if data_file:
		if not key1 in data:
			data[key1] = {}
		data[key1][key2] = sections_order
		
		var json_to_save: String = JSON.print(data, "\t")
		data_file.seek(0)
		data_file.store_string(json_to_save)
		data_file.close()
	else:
		push_error("Could not open file to save preference: %s->%s"
				% [key1, key2])


func get_media_opener(mime_type: String) -> Dictionary:
	"""
	Returns:
		A valid media opener for the given MIME type or the default media
		opener if none found
	"""
	var key: String = "media_openers"
	var data: Dictionary = get_data()
	var media_opener: Dictionary = DEFAULT_MEDIA_OPENER.duplicate(true)
	if data.get(key, {}).get(mime_type) is Dictionary:
		var warn: bool = false
		if data[key][mime_type].get("command") is String:
			media_opener["command"] = data[key][mime_type]["command"]
		else:
			warn = true
		if data[key][mime_type].get("use_streaming") is bool:
			media_opener["use_streaming"] = \
					data[key][mime_type]["use_streaming"]
		else:
			warn = true
		
		if warn:
			var text: String = "Values of the media opener for MIME type"
			text += " \"%s\" are missing or of an invalid type" % mime_type
			push_warning(text)
	return media_opener


func save_media_opener(mime_type: String, media_opener: Dictionary) -> void:
	assert(_is_media_opener(media_opener))
	
	var key: String = "media_openers"
	# `get_data()` before `_open_data_file()` to ensure no problems occur due 
	# to trying to open `DATA_FILEPATH` twice
	var data: Dictionary = get_data()
	var data_file: File = _open_data_file(File.WRITE)
	if data_file:
		if not key in data:
			data[key] = {}
		data[key][mime_type] = media_opener
		
		var json_to_save: String = JSON.print(data, "\t")
		data_file.seek(0)
		data_file.store_string(json_to_save)
		data_file.close()
	else:
		push_error("Could not open file to save preference: %s" % key)


func get_search_options() -> Dictionary:
	""" Options that should be applied to every claim search """
	var key: String = "permanent_search_options"
	var data: Dictionary = get_data()
	var options: Dictionary = DEFAULTS[key]
	if data.get(key) is Dictionary:
		options = data.get(key)
	return options


func save_search_options(options: Dictionary) -> void:
	var key: String = "permanent_search_options"
	# `get_data()` before `_open_data_file()` to ensure no problems occur due
	# to trying to open `DATA_FILEPATH` twice
	var data: Dictionary = get_data()
	var data_file: File = _open_data_file(File.WRITE)
	if data_file:
		if not key in data:
			data[key] = {}
		data[key] = options
		
		var json_to_save: String = JSON.print(data, "\t")
		data_file.seek(0)
		data_file.store_string(json_to_save)
		data_file.close()
	else:
		push_error("Could not open file to save preference: %s" % key)


func get_stop_daemon_on_exit() -> bool:
	"""
	Returns:
		Whether or not to stop the lbrynet daemon when the program exits
	"""
	var key: String = "stop_daemon_on_exit"
	var data: Dictionary = get_data()
	var stop_daemon_on_exit: bool = DEFAULTS[key]
	if data.get(key) is bool:
		stop_daemon_on_exit = data.get(key)
	return stop_daemon_on_exit


func save_stop_daemon_on_exit(stop: bool) -> void:
	var key: String = "stop_daemon_on_exit"
	# `get_data()` before `_open_data_file()` to ensure no problems occur due 
	# to trying to open `DATA_FILEPATH` twice
	var data: Dictionary = get_data()
	var data_file: File = _open_data_file(File.WRITE)
	if data_file:
		data[key] = stop
		
		var json_to_save: String = JSON.print(data, "\t")
		data_file.seek(0)
		data_file.store_string(json_to_save)
		data_file.close()
	else:
		push_error("Could not open file to save preference: %s" % key)


func get_comment_server() -> String:
	"""
	Returns:
		The URL used to retrieve comments from
	"""
	var key: String = "comment_server"
	var data: Dictionary = get_data()
	var comment_server: String = DEFAULTS[key]
	if data.get(key) is String:
		comment_server = data.get(key)
	return comment_server


func save_comment_server(server: String) -> void:
	var key: String = "comment_server"
	# `get_data()` before `_open_data_file()` to ensure no problems occur due
	# to trying to open `DATA_FILEPATH` twice
	var data: Dictionary = get_data()
	var data_file: File = _open_data_file(File.WRITE)
	if data_file:
		data[key] = server
		
		var json_to_save: String = JSON.print(data, "\t")
		data_file.seek(0)
		data_file.store_string(json_to_save)
		data_file.close()
	else:
		push_error("Could not open file to save preference: %s" % key)


func get_data() -> Dictionary:
	"""
	Returns:
		The parsed JSON content of `DATA_FILEPATH` or an empty dictionary if it
		could not be opened or parsed
	"""
	var data_file: File = _open_data_file(File.READ)
	var data: Dictionary = {}
	if data_file:
		data = _get_parsed_json_as_dict(data_file)
		data_file.close()
	return data


func _open_file(filepath: String, flag: int) -> File:
	"""
	Returns:
		The opened file (creates a new one if it doesn't exist) for reading 
		and writing. `null` if an error occurs
	"""
	var file: File = File.new()
	var err: int = file.open(filepath, flag)
	if err == OK:
		return file
	else:
		file.close()
		push_error("Failed to open the file: %s (Error: %d)" % [filepath, err])
		return null


func _open_data_file(flag: int) -> File:
	"""
	Returns:
		The data file. If it doesn't exist then a new file is created and
		returned. If an error occurs then `null` is returned
	"""
	var data_file: File = _open_file(DATA_FILEPATH, flag)
	if (
			(flag == File.READ or flag == File.READ_WRITE)
			and (data_file and not _data_file_is_valid(data_file))
	):
		data_file.close()
		data_file = _open_file(DATA_FILEPATH, File.WRITE)
		if data_file:
			_initialize_data_file(data_file)
		data_file.close()
		data_file = _open_file(DATA_FILEPATH, flag)
	return data_file


func _data_file_is_valid(data_file: File) -> bool:
	"""
	Returns:
		If `data_file` encodes a valid dictionary then `true` otherwise `false`
	"""
	var content: String = data_file.get_as_text()
	var json_result: JSONParseResult = JSON.parse(content)
	if json_result.get_error() == OK:
		return true
	return false


func _initialize_data_file(data_file: File) -> void:
	var store_json: String = JSON.print(DEFAULTS, "\t")
	data_file.seek(0)
	data_file.store_string(store_json)


func _get_parsed_json_as_dict(data_file: File) -> Dictionary:
	"""
	Returns:
		The parsed JSON as a Dictionary from the file. An empty Dictionary is
		returned if an error occurs
	"""
	var content: String = data_file.get_as_text()
	var json_result: JSONParseResult = JSON.parse(content)
	var return_result: Dictionary = {}
	if json_result.get_error() != OK:
		push_error("Failed to parse the JSON on line %d: %s (%d)"
				% [
					json_result.get_error_line(), 
					json_result.get_error_string(), 
					json_result.get_error()
				])
	elif typeof(json_result.get_result()) != TYPE_DICTIONARY:
		push_error("The JSON does not encode a dictionary")
	else:
		return_result = json_result.get_result()
	return return_result


func _is_media_opener(media_opener: Dictionary) -> bool:
	""" Checks if the argument is a valid media opener """
	return ((media_opener.get("command") is String)
			and (media_opener.get("use_streaming") is bool))
